/* * Copyright (c) 2014. * * BaasBox - info@baasbox.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.baasbox.dao; import com.baasbox.dao.exception.InvalidScriptException; import com.baasbox.dao.exception.ScriptAlreadyExistsException; import com.baasbox.dao.exception.ScriptException; import com.baasbox.dao.exception.SqlInjectionException; import com.baasbox.db.DbHelper; import com.baasbox.util.QueryParams; import com.fasterxml.jackson.databind.JsonNode; import org.apache.commons.codec.binary.StringUtils; import org.apache.commons.codec.binary.Base64; import com.orientechnologies.orient.core.command.OCommand; import com.orientechnologies.orient.core.command.OCommandRequest; import com.orientechnologies.orient.core.db.ODatabaseComplex; import com.orientechnologies.orient.core.db.record.ODatabaseRecordTx; import com.orientechnologies.orient.core.db.record.OIdentifiable; import com.orientechnologies.orient.core.db.record.OTrackedList; import com.orientechnologies.orient.core.id.ORecordId; import com.orientechnologies.orient.core.index.OIndex; import com.orientechnologies.orient.core.record.ORecordInternal; import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.record.impl.ODocumentHelper; import com.orientechnologies.orient.core.sql.OCommandSQL; import com.orientechnologies.orient.core.storage.ORecordCallback; import com.orientechnologies.orient.core.storage.OStorage; import com.baasbox.service.logging.BaasBoxLogger; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.regex.Pattern; /** * Created by Andrea Tortorella on 10/06/14. */ public class ScriptsDao { public enum ENCODED_TYPE{ BASE64; public String decode(String toDecode){ String toReturn=""; switch (this){ case BASE64: byte[] decoded = Base64.decodeBase64(toDecode); toReturn= StringUtils.newStringUtf8(decoded); } BaasBoxLogger.debug("Decode new script sent in {}. Input: {} Output: {}", BASE64,toDecode,toReturn); return toReturn; } } public static final String MODEL_NAME = "_BB_Script"; public static final String NAME= "name"; public static final String CODE= "code"; public static final String LANG= "lang"; public static final String ENCODED= "encoded"; public static final String LIB= "library"; public static final String LOCAL_STORAGE= "_storage"; public static final String INVALID = "_invalid"; public static final String DATE ="_creation_date"; public static final String ACTIVE = "active"; private static final Pattern VALID_NAME_PATTERN = Pattern.compile("([a-zA-Z_][a-zA-Z_0-9]*)(\\.([a-zA-Z][a-zA-Z_0-9]*))+"); private static final String INDEX =MODEL_NAME+"."+NAME; private final ODatabaseRecordTx db; protected ScriptsDao(){ db = DbHelper.getConnection(); } public static ScriptsDao getInstance(){ return new ScriptsDao(); } public boolean delete(String name){ ODocument doc = getByName(name); if (doc == null) { return false; } else { doc.delete(); return true; } } //todo update script //todo read/update store //todo clear log public ODocument save(ODocument doc){ doc.save(); return doc; } public ODocument create(String name,String language,String code,boolean isLibrary,boolean active,JsonNode initialStore) throws ScriptException{ return create( name, language, code, isLibrary, active, initialStore, null); } public ODocument create(String name, String language, String code, boolean isLibrary, boolean active, JsonNode initialStorage,String encodedValue) throws ScriptException { if (BaasBoxLogger.isTraceEnabled()) BaasBoxLogger.trace("Method Start"); checkValidName(name); if (exists(name)){ throw new ScriptAlreadyExistsException("Script "+name+" already exists"); } if (encodedValue!=null){ code=ENCODED_TYPE.valueOf(encodedValue.toUpperCase()).decode(code); } ODocument doc = createPrivileged(name,language,code,isLibrary,active,initialStorage); if (BaasBoxLogger.isTraceEnabled()) BaasBoxLogger.trace("Method End"); return doc; } private ODocument createPrivileged(String name,String language,String code,boolean isLibrary,boolean active,JsonNode initialStore){ ODocument doc = makeScript(name, language, code, isLibrary, active, initialStore); save(doc); return doc; } public ODocument update(String name, String code) throws ScriptException{ ODocument doc = getByName(name); if (doc == null){ throw new ScriptException("Script: "+name+" does not exists"); } OTrackedList<String> codeVersions =doc.field(CODE); codeVersions.add(0, code); save(doc); return doc; } //used by the service public void revertToLastVersion(ODocument updated) { OTrackedList<String> code = updated.<OTrackedList<String>>field(CODE); code.remove(0); save(updated); } private ODocument makeScript(String name, String language, String code, boolean isLibrary, boolean active, JsonNode initialStore) { ODocument doc = new ODocument(MODEL_NAME); doc.field(NAME,name); doc.field(LANG,language); List<String> codes = Collections.singletonList(code); doc.field(CODE,codes); doc.field(DATE,new Date()); doc.field(LIB,isLibrary); doc.field(ACTIVE,active); ODocument local = new ODocument(); if (initialStore!=null){ local.fromJSON(initialStore.toString()); } doc.field(LOCAL_STORAGE,local); doc.field(INVALID,false); return doc; } public List<ODocument> getAll(QueryParams params) throws SqlInjectionException{ if (BaasBoxLogger.isTraceEnabled()) BaasBoxLogger.trace("Method Start"); List<ODocument> docs= null; params = params==null?QueryParams.getInstance():params; OCommandRequest command = DbHelper.selectCommandBuilder(MODEL_NAME,params.justCountTheRecords(),params); docs = DbHelper.commandExecute(command,params.getParams()); if (BaasBoxLogger.isTraceEnabled()) BaasBoxLogger.trace("Method End"); return docs; } public List<ODocument> getAll() throws SqlInjectionException { return getAll(QueryParams.getInstance()); } public static void checkValidName(String name) throws ScriptException{ if (name== null||name.trim().length()==0){ throw new InvalidScriptException("Script must have non empty name"); } if (!VALID_NAME_PATTERN.matcher(name).matches()){ throw new InvalidScriptException("Script names must be composed of letters numbers and underscores, and cannot start with numbers. Script must have at least one namespace part. Valid example: mynamespace.myscript"); } if (isInternalName(name)){ throw new InvalidScriptException("User scripts cannot belong to 'baasbox' namespace"); } } private static boolean isInternalName(String name){ return name!=null&&name.startsWith("baasbox"); } public boolean exists(String name){ if (BaasBoxLogger.isTraceEnabled())BaasBoxLogger.trace("Method Start"); boolean exists = findByName(name)!= null; if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Exists "+exists); if (BaasBoxLogger.isTraceEnabled())BaasBoxLogger.trace("Method End"); return exists; } public ODocument getByName(String name){ if (BaasBoxLogger.isTraceEnabled())BaasBoxLogger.trace("Method Start"); OIdentifiable id = findByName(name); ODocument doc = id == null?null:(ODocument)db.load(id.getIdentity()); if (BaasBoxLogger.isTraceEnabled())BaasBoxLogger.trace("Method End"); return doc; } private OIdentifiable findByName(String name){ OIndex idx = db.getMetadata().getIndexManager().getIndex(INDEX); return (OIdentifiable)idx.get(name); } public void invalidate(ODocument script) { script.field(INVALID,true); save(script); } public ODocument getByNameLocked(String name) { OIndex idx = db.getMetadata().getIndexManager().getIndex(INDEX); OIdentifiable idf =(OIdentifiable)idx.get(name); if (idf == null){ return null; } ODocument doc = db.load(idf.getIdentity(), null, false, false, OStorage.LOCKING_STRATEGY.KEEP_EXCLUSIVE_LOCK); return doc; } public boolean activate(ODocument script, boolean activate) { boolean current =script.<Boolean>field(ACTIVE); script.field(ACTIVE,activate); script.save(); return current!=activate; } }